﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using DevComponents.DotNetBar;
using DevComponents.Editors;
using NHibernate.MappingGenerator.Properties;
using Microsoft.SqlServer.Management.Smo;
using System.Xml;

namespace NHibernate.MappingGenerator
{
	public partial class MainForm : Office2007Form
	{
		private Database selectedDatabase;

		public MainForm()
		{
			InitializeComponent();
		}

		private void ListDatabases()
		{
			databasesComboBox.Items.Clear();

			if (GeneratorContext.CurrentSqlServer != null)
			{
				foreach (Database database in GeneratorContext.CurrentSqlServer.Databases)
				{
					DevComponents.Editors.ComboItem item = new ComboItem();

					item.Text = database.Name;
					item.Image = Resources.icon_database;

					databasesComboBox.Items.Add(item);
				}
			}
		}

		private void ListTables()
		{
			tablesListView.Items.Clear();
			if (GeneratorContext.CurrentSqlServer != null)
			{
				DevComponents.Editors.ComboItem item = (DevComponents.Editors.ComboItem)databasesComboBox.SelectedItem;
				selectedDatabase = GeneratorContext.CurrentSqlServer.Databases[item.Text];

				try
				{
					foreach (Table table in selectedDatabase.Tables)
					{
						ListViewItem tableItem = new ListViewItem();

						tableItem.Text = table.Name;
						tableItem.Tag = table.Schema;
						tableItem.ImageIndex = 0;

						tablesListView.Items.Add(tableItem);
					}
				}
				catch (Exception ex)
				{
					MessageBox.Show(ex.InnerException.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
			}
		}

		private XmlElement GenerateXmlRoot(XmlDocument document, string assemblyName, string classNamespace)
		{
			#region XML Root
			XmlDeclaration declaration = document.CreateXmlDeclaration("1.0", "utf-8", null);
			document.AppendChild(declaration);

			XmlElement elementRoot = document.CreateElement("hibernate-mapping");

			XmlAttribute assemblyAttr = document.CreateAttribute("assembly");
			assemblyAttr.Value = assemblyName;

			XmlAttribute namespaceAttr = document.CreateAttribute("namespace");
			namespaceAttr.Value = classNamespace;

			XmlAttribute xmlnsAttr = document.CreateAttribute("xmlns");
			xmlnsAttr.Value = "urn:nhibernate-mapping-2.2";

			elementRoot.Attributes.Append(assemblyAttr);
			elementRoot.Attributes.Append(namespaceAttr);
			elementRoot.Attributes.Append(xmlnsAttr);
			#endregion

			return elementRoot;
		}

        private void AddFileHeader(StringBuilder sb, string classNamespace)
        {
            sb.AppendLine("using System;");
            sb.AppendLine("using System.Collections.Generic;");
            sb.AppendLine("using System.Linq;");
            sb.AppendLine("using System.Text;");
            sb.AppendLine();

            sb.AppendLine(string.Format("namespace {0}", classNamespace));
            sb.AppendLine("{");
        }

		private string GetClrDataType(DataType sqlDataType)
		{
			string crlName = string.Empty;

			switch (sqlDataType.SqlDataType)
			{
				case SqlDataType.BigInt:
					crlName = typeof(System.Int64).ToString();
					break;
				case SqlDataType.Bit:
					crlName = typeof(System.Boolean).ToString();
					break;
				case SqlDataType.Date:
				case SqlDataType.DateTime:
				case SqlDataType.DateTime2:
				case SqlDataType.DateTimeOffset:
				case SqlDataType.SmallDateTime:
				case SqlDataType.Time:
				case SqlDataType.Timestamp:
					crlName = typeof(System.DateTime).ToString();
					break;
				case SqlDataType.Decimal:
				case SqlDataType.Float:
					crlName = typeof(System.Double).ToString();
					break;
				case SqlDataType.SmallInt:
				case SqlDataType.Int:
					crlName = typeof(System.Int32).ToString();
					break;
				case SqlDataType.Char:
				case SqlDataType.NChar:
				case SqlDataType.NText:
				case SqlDataType.NVarChar:
				case SqlDataType.NVarCharMax:
				case SqlDataType.Text:
				case SqlDataType.VarChar:
				case SqlDataType.VarCharMax:
					crlName = typeof(System.String).ToString();
					break;
				case SqlDataType.TinyInt:
					crlName = typeof(System.Byte).ToString();
					break;
				case SqlDataType.UniqueIdentifier:
					crlName = typeof(System.Guid).ToString();
					break;
				default:
					crlName = string.Empty;
					break;
			}

			return crlName;
		}

		public void GenerateXmlFiles()
		{
			if (tablesListView.SelectedItems.Count > 0)
			{
				string folderPath = GeneratorSettings.DirectoryPath;
				string classNamespace = GeneratorSettings.Namespace;
				bool generateMultiplFiles = GeneratorSettings.GenerateFileByTable;
				string assemblyName = GeneratorSettings.AssemblyName;
				string idGenerator = GeneratorSettings.IdGenerator;
                bool generateClassFile = GeneratorSettings.GenerateFileClass;

				string fileName = string.Empty;
				string fullName = string.Empty;
                string classFileName = string.Empty;

				XmlDocument document = null;
				XmlElement elementRoot = null;

                StringBuilder sbOutput = null;

				if (!generateMultiplFiles)
				{
					document = new XmlDocument();
					elementRoot = GenerateXmlRoot(document, assemblyName, classNamespace);
					fileName = string.Format("{0}.hbm.xml", classNamespace);
					fullName = Path.Combine(folderPath, fileName);
                    classFileName = Path.Combine(folderPath, string.Format("{0}.cs", classNamespace));

                    if (generateClassFile)
                    {
                        sbOutput = new StringBuilder();
                        AddFileHeader(sbOutput, classNamespace);
                    }
				}

                foreach (ListViewItem viewItem in tablesListView.SelectedItems)
                {
                    string tableName = viewItem.Text;
                    string schema = viewItem.Tag.ToString();

                    if (generateMultiplFiles)
                    {
                        document = new XmlDocument();
                        elementRoot = GenerateXmlRoot(document, assemblyName, classNamespace);
                        fileName = string.Format("{0}.hbm.xml", tableName);
                        fullName = Path.Combine(folderPath, fileName);
                        classFileName = Path.Combine(folderPath, string.Format("{0}.cs", tableName));

                        if (generateClassFile)
                        {
                            sbOutput = new StringBuilder();
                            AddFileHeader(sbOutput, classNamespace);
                        }
                    }

                    if (generateClassFile)
                    {
                        sbOutput.AppendLine(string.Format("\tpublic class {0}", tableName));
                        sbOutput.AppendLine("\t{");
                    }

                    Table table = selectedDatabase.Tables[tableName, schema];

                    if (table != null)
                    {
                        XmlNode classNode = document.CreateNode(XmlNodeType.Element, "class", null);

                        elementRoot.AppendChild(classNode);

                        XmlAttribute classNameAttr = document.CreateAttribute("name");
                        classNameAttr.Value = tableName;

                        XmlAttribute tableAttr = document.CreateAttribute("table");
                        tableAttr.Value = tableName;

                        XmlAttribute schemaAttr = document.CreateAttribute("schema");
                        schemaAttr.Value = schema;

                        classNode.Attributes.Append(classNameAttr);
                        classNode.Attributes.Append(tableAttr);
                        classNode.Attributes.Append(schemaAttr);

                        foreach (Column column in table.Columns)
                        {
                            if (column.InPrimaryKey)
                            {
                                classNode.AppendChild(document.CreateNode(XmlNodeType.Element, "id", null));

                                classNode.LastChild.Attributes.Append(document.CreateAttribute("name"));
                                classNode.LastChild.Attributes.Append(document.CreateAttribute("access"));
                                classNode.LastChild.Attributes.Append(document.CreateAttribute("column"));

                                classNode.LastChild.Attributes["name"].Value = column.Name;
                                classNode.LastChild.Attributes["access"].Value = "property";
                                classNode.LastChild.Attributes["column"].Value = column.Name;

                                XmlElement generator = document.CreateElement("generator");
                                generator.Attributes.Append(document.CreateAttribute("class"));
                                generator.Attributes["class"].Value = idGenerator;

                                classNode.LastChild.AppendChild(generator);
                            }
                            else if (!column.IsForeignKey)
                            {
                                classNode.AppendChild(document.CreateNode(XmlNodeType.Element, "property", null));

                                classNode.LastChild.Attributes.Append(document.CreateAttribute("name"));
                                classNode.LastChild.Attributes.Append(document.CreateAttribute("type"));
                                classNode.LastChild.Attributes.Append(document.CreateAttribute("column"));

                                classNode.LastChild.Attributes["name"].Value = column.Name;
                                classNode.LastChild.Attributes["type"].Value = GetClrDataType(column.DataType);
                                classNode.LastChild.Attributes["column"].Value = column.Name;
                            }

                            if (generateClassFile)
                            {
                                if (!column.IsForeignKey)
                                {
                                    if (column.Nullable)
                                        sbOutput.AppendLine(string.Format("\t\tpublic virtual Nullable<{0}> {1} {{ get; set; }}", GetClrDataType(column.DataType), column.Name));
                                    else
                                        sbOutput.AppendLine(string.Format("\t\tpublic virtual {0} {1} {{ get; set; }}", GetClrDataType(column.DataType), column.Name));
                                }
                            }
                        }

                        foreach (ForeignKey foreignKey in table.ForeignKeys)
                        {
                            // <many-to-one name="Country" class="Country" column="Country_Id" />
                            classNode.AppendChild(document.CreateNode(XmlNodeType.Element, "many-to-one", null));

                            classNode.LastChild.Attributes.Append(document.CreateAttribute("name"));
                            classNode.LastChild.Attributes.Append(document.CreateAttribute("class"));
                            classNode.LastChild.Attributes.Append(document.CreateAttribute("column"));

                            classNode.LastChild.Attributes["name"].Value = foreignKey.ReferencedTable;
                            classNode.LastChild.Attributes["class"].Value = foreignKey.ReferencedTable;
                            classNode.LastChild.Attributes["column"].Value = foreignKey.Columns[0].Name;

                            if (generateClassFile)
                            {
                                sbOutput.AppendLine(string.Format("\t\tpublic virtual {0} {1} {{ get; set; }}", foreignKey.ReferencedTable, foreignKey.ReferencedTable));
                            }
                        }
                    }

                    if (generateMultiplFiles)
                    {
                        document.AppendChild(elementRoot);
                        document.Save(fullName);

                        if (generateClassFile)
                        {
                            sbOutput.AppendLine("\t}");
                            sbOutput.AppendLine("}");
                            using (StreamWriter sw = new StreamWriter(classFileName))
                            {
                                sw.Write(sbOutput.ToString());
                            }
                        }
                    }
                }

				if (!generateMultiplFiles)
				{
					document.AppendChild(elementRoot);
					document.Save(fullName);

                    if (generateClassFile)
                    {
                        sbOutput.AppendLine("\t}");
                        sbOutput.AppendLine("}");
                        using (StreamWriter sw = new StreamWriter(classFileName))
                        {
                            sw.Write(sbOutput.ToString());
                        }
                    }
				}
			}
		}

		#region Events
		private void MainForm_Load(object sender, EventArgs e)
		{
			ListDatabases();
		}

		private void databasesComboBox_SelectedIndexChanged(object sender, EventArgs e)
		{
			ListTables();
		}

		private void tablesListView_SelectedIndexChanged(object sender, EventArgs e)
		{
			columnsListView.Items.Clear();
			columnsListView.Groups.Clear();

			foreignKeysListView.Items.Clear();
			foreignKeysListView.Groups.Clear();

			if (tablesListView.SelectedItems.Count > 0)
			{
				foreach (ListViewItem viewItem in tablesListView.SelectedItems)
				{
					string tableName = viewItem.Text;
					string schema = viewItem.Tag.ToString();

					Table table = selectedDatabase.Tables[tableName, schema];

					if (table != null)
					{
						string keyGroup = string.Format("{0}.{1}", schema, tableName);
						ListViewGroup viewGroup = new ListViewGroup(keyGroup, keyGroup);

						columnsListView.Groups.Add(viewGroup);
						foreignKeysListView.Groups.Add(viewGroup);

						foreach (Column column in table.Columns)
						{
							ListViewItem item = new ListViewItem();

                            item.Name = table.Name + "." + column.Name;
                            item.Text = column.Name;
							item.Group = viewGroup;
                            item.Checked = true;
							item.ImageIndex = 1;
							if (column.InPrimaryKey)
								item.ImageIndex = 2;

							if (column.IsForeignKey)
								item.ImageIndex = 3;

							item.SubItems.Add(new ListViewItem.ListViewSubItem() { Text = column.DataType.Name });
							item.SubItems.Add(new ListViewItem.ListViewSubItem() { Text = column.Properties["Length"].Value.ToString() });
							item.SubItems.Add(new ListViewItem.ListViewSubItem() { Text = column.Nullable ? "Yes" : "No" });

							columnsListView.Items.Add(item);
						}

						foreach (ForeignKey foreignKey in table.ForeignKeys)
						{
							ListViewItem item = new ListViewItem();

							item.Text = foreignKey.Name;
							item.Group = viewGroup;
							item.ImageIndex = 4;

							item.SubItems.Add(new ListViewItem.ListViewSubItem() { Text = foreignKey.ReferencedTable });
							item.SubItems.Add(new ListViewItem.ListViewSubItem() { Text = foreignKey.Columns[0].Name });

							foreignKeysListView.Items.Add(item);
						}
					}
				}
			}
		}

		private void generateToolStripButton_Click(object sender, EventArgs e)
		{
			GenerateXmlFiles();
		}

		private void settingsToolStripButton_Click(object sender, EventArgs e)
		{
			SettingsForm form = new SettingsForm();
			form.ShowDialog(this);
		}

        private void compareToolStripButton_Click(object sender, EventArgs e)
        {
            if (columnsListView.Items.Count>0)
            {
                StringBuilder sbOutput = new StringBuilder();

                StringBuilder equals = new StringBuilder();
                StringBuilder hashCode = new StringBuilder(); 

                string tableName = columnsListView.Items[0].Name.Split('.')[0];
                string tabs = string.Join<string>("", Enumerable.Repeat<string>("\t", 3));

                foreach (ListViewItem viewItem in columnsListView.Items)
                {
                    if (viewItem.Checked)
                    {
                        equals.AppendLine(string.Format("{1}x.{0} == y.{0} &&", viewItem.Text, tabs));
                        hashCode.AppendLine(string.Format("{1}obj.{0}.GetHashCode() ^", viewItem.Text, tabs));
                    }
                }

                sbOutput.AppendLine(string.Format("private class {0}Comparer : IEqualityComparer<{0}>", tableName));
                sbOutput.AppendLine("{");
                sbOutput.AppendLine(string.Format("\tpublic bool Equals({0} x, {0} y)", tableName));
                sbOutput.AppendLine("\t{");
                sbOutput.AppendLine("\t\tif (object.ReferenceEquals(x, y)) return true;");
                sbOutput.AppendLine();
                sbOutput.AppendLine("\t\tif (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null)) return false;");
                sbOutput.AppendLine();
                sbOutput.AppendFormat("\t\treturn {0};", equals.Remove(equals.Length - 5, 5).ToString());
                sbOutput.AppendLine("\t}");
                sbOutput.AppendLine();
                sbOutput.AppendLine(string.Format("\tpublic int GetHashCode({0} obj)", tableName));
                sbOutput.AppendLine("\t{");
                sbOutput.AppendLine("\t\tif (object.ReferenceEquals(obj, null)) return 0;");
                sbOutput.AppendLine();
                sbOutput.AppendFormat("\t\treturn {0};", hashCode.Remove(hashCode.Length - 4, 4).ToString());
                sbOutput.AppendLine("\t}");
                sbOutput.AppendLine("}");

                ComparerForm form = new ComparerForm(sbOutput);
                form.ShowDialog(this);

            }
        }
        #endregion
    }
}
